#include "svmIO.h"

static char *line = NULL;
static int max_line_len;
static char* readline(FILE *input)
{
	int len;
	
	if(fgets(line,max_line_len,input) == NULL)
		return NULL;

	while(strrchr(line,'\n') == NULL)
	{
		max_line_len *= 2;
		line = (char *) realloc(line,max_line_len);
		len = (int) strlen(line);
		if(fgets(line+len,max_line_len-len,input) == NULL)
			break;
	}
	return line;
}

void exitInputError(int line_num)
{
	fprintf(stderr,"Wrong input format at line %d\n", line_num);
	exit(1);
}

std::pair<std::string, Parameters> matchedParameters[] = {
	std::pair<std::string, Parameters>("gamma",       p_gamma),
	std::pair<std::string, Parameters>("coef",        p_coef0),
	std::pair<std::string, Parameters>("degree",      p_degree),
	std::pair<std::string, Parameters>("rho",         p_rho),
	std::pair<std::string, Parameters>("kernel_type", p_kernel_type),
	std::pair<std::string, Parameters>("svm_type",    p_svm_type),
	std::pair<std::string, Parameters>("nr_class",    p_nr_class),
	std::pair<std::string, Parameters>("total_sv",    p_total_sv),
	std::pair<std::string, Parameters>("label",       p_label),
	std::pair<std::string, Parameters>("nr_sv",       p_nr_sv),
	std::pair<std::string, Parameters>("SV",          p_SV)
};

// Map to associate the strings with the enum values
std::map<std::string, Parameters> mapStringToParameters(matchedParameters, matchedParameters + sizeof(matchedParameters)/sizeof(matchedParameters[0]));


float readFloat(FILE* input) {
	char currentCharacter;
	char floatBuffer[50];
	char* bufferPointer = floatBuffer;
	do {
		currentCharacter = fgetc(input);
		*(bufferPointer) = currentCharacter;
		bufferPointer++;
	} while ((currentCharacter >= '0' && currentCharacter <= '9') ||
		(currentCharacter == '.') || (currentCharacter == '-') ||
		(currentCharacter == 'e') || (currentCharacter == 'E'));
	bufferPointer--;
	*(bufferPointer) = 0;
	float result;
	sscanf(floatBuffer, "%f", &result);
	ungetc(currentCharacter, input);
	return result;
}

int readModel(const char* filename, float** alpha, float** supportVectors, int* nSVOut, int* nDimOut, Kernel_params* kp, float* p_class1Label, float* p_class2Label) {

	FILE* input = fopen(filename, "r");
	if (input == 0) {
		printf("Model File not found\n");
		return 0;
	}
	std::string line_description;

	char currentLine[80];
	int parsingParameters = 1;
	while (parsingParameters > 0) {
		fgets(currentLine, 80, input);
		line_description.clear();
		int j = 0;
		while(isalpha(currentLine[j]) || currentLine[j]=='_')
		{
			line_description.push_back(currentLine[j]);
			j++;
		}

		int foundParameter = 0;
		switch (mapStringToParameters[line_description]) {
			case p_gamma:
				//printf("Reading gamma - %s\n",line_description.c_str() );
				sscanf(&currentLine[line_description.length()+1], "%f", &(kp->gamma));
				foundParameter = 1;
				printf("Found gamma = %f\n",kp->gamma);
				break;
			case p_total_sv:
				sscanf(&currentLine[line_description.length()+1], "%d", nSVOut);
				foundParameter = 1;
				break;
			case p_rho:
				sscanf(&currentLine[line_description.length()+1], "%f", &(kp->b));
				kp->b = -kp->b;
				foundParameter = 1;
				break;
			case p_degree:
				sscanf(&currentLine[line_description.length()+1], "%d", &(kp->degree));
				foundParameter = 1;
				printf("Found degree = %d\n", kp->degree);
				break;
			case p_coef0:
				//printf("Reading coef0 - %s\n",line_description.c_str() );
				sscanf(&currentLine[line_description.length()+1], "%f", &(kp->coef0));
				foundParameter = 1;
				printf("Found coef0=%f\n",kp->coef0);
				break;
			case p_kernel_type:
				kp->kernel_type.insert(0,currentLine+line_description.length()+1); 
				foundParameter = 1;
				break;
			case p_label:
				int intLabel1, intLabel2;
				sscanf(&currentLine[line_description.length()+1], "%d %d", &intLabel1, &intLabel2);
				*p_class1Label = (float)intLabel1;
				*p_class2Label = (float)intLabel2;
				foundParameter=1;
				break;
			case p_SV:
				parsingParameters = 0;
			default:
				foundParameter = 1;
				break;
		}
		if (foundParameter == 0) {
			printf("Malformed model: %s\n", currentLine);
			return 0;
		}
	}

	int nSV = *nSVOut;
	float* localAlpha = (float*)malloc(sizeof(float)*nSV);
	float* currentSV = (float*)malloc(sizeof(float)*65536);//Don't know dimension
	//TODO 3. guessing it will be less than 65536.  Otherwise, we'll fail, This should be rewritten

	localAlpha[0] = readFloat(input);
	int nDimension = 0;
	char currentCharacter = 0;
	while (currentCharacter != '\n') {
		currentCharacter = fgetc(input);
		if (currentCharacter == ':') {
			float currentCoordinate = readFloat(input);
			currentSV[nDimension] = currentCoordinate;
			nDimension++;
		}
	}
	float* localSV = (float*)malloc(sizeof(float)*nSV*nDimension);
	for(int dim = 0; dim < nDimension; dim++) {
		localSV[dim*nSV] = currentSV[dim]; 
	}
	free(currentSV);

	for(int sv = 1; sv < nSV; sv++) {
		localAlpha[sv] = readFloat(input);
		for(int dim = 0; dim < nDimension; dim++) {
			do {
				currentCharacter = fgetc(input);
			} while(currentCharacter != ':');
			localSV[nSV*dim + sv] = readFloat(input);
		}
		do {
			currentCharacter = fgetc(input);
		} while (((currentCharacter < '0') || (currentCharacter > '9')) && (currentCharacter != '.') && (currentCharacter != '-') && (currentCharacter != 'e') && (currentCharacter != 'E') && (currentCharacter >= 0));
		ungetc(currentCharacter, input);

	}

	*alpha = localAlpha;
	*supportVectors = localSV;
	*nSVOut = nSV;
	*nDimOut = nDimension;
	return 1;
}

int readSvm(const char* filename, float** pData, float** pLabels, int* pNPoints, int* pDimension, float** pTransposedData) {
	FILE* inputFilePointer = fopen(filename, "r");
	if (inputFilePointer == NULL) {
		printf("File not found:%s\n",filename);
		exit(1);
	}

	int npoints = 0;
	int dimension=-1;
	max_line_len = 1024;
	line = (char*)malloc(max_line_len);
	while(readline(inputFilePointer)!=NULL)
	{
		char *p = strtok(line," \t");
		while(1)
		{
			p = strtok(NULL," \t");
			if(p == NULL || *p == '\n')
				break;
			int tempDim = atoi(p); //addition char':' will be ignored
			if(tempDim > dimension) {
				dimension = tempDim;
			}
		}
		++npoints;
	}
	rewind(inputFilePointer);
	*(pNPoints) = npoints;
	*(pDimension) = dimension;

	float* data = (float*)malloc(sizeof(float)*npoints*dimension);
	memset(data,0,npoints*dimension*sizeof(float));
	*(pData) = data;
	float* labels = (float*)malloc(sizeof(float)*npoints);
	*(pLabels) = labels;
	float* transposedData = NULL;
	if(pTransposedData != NULL) {
		transposedData = (float*)malloc(sizeof(float)*npoints*dimension);
		memset(transposedData,0,sizeof(float)*npoints*dimension);
		*(pTransposedData) = transposedData;
	}

	char * endPtr;
	for(int i=0;i<npoints;i++) {
		int instMaxIndex = -1;
		readline(inputFilePointer);
		char * cLabel = strtok(line," \t");
		labels[i] = (float)strtod(cLabel,&endPtr);
		if(endPtr == cLabel)
			exitInputError(i+1);
		while(1) {
			char * cIdx = strtok(NULL,":");
			char * cVal = strtok(NULL," \t");

			if(cVal == NULL) break;

			errno = 0;
			int idx = (int)strtol(cIdx,&endPtr,10);
			if(endPtr == cIdx || errno !=0 || *endPtr != '\0' || idx <= instMaxIndex)
				exitInputError(i+1);
			else
				instMaxIndex = idx;

			errno = 0;
			float val = (float)strtod(cVal,&endPtr);
			if(endPtr == cVal || errno !=0 || (*endPtr != '\0' && !isspace(*endPtr)))
				exitInputError(i+1);
			data[(idx-1)*npoints+i] = val;
			if(transposedData != NULL)
				transposedData[i*dimension+idx-1] = val;
		}
	}
	fclose(inputFilePointer);
	return 1;
}

void printModel(const char* outputFile, Kernel_params kp, float* alpha, float* labels, float* data, int nPoints, int nDimension, float epsilon) { 
	printf("Output File: %s\n", outputFile);
	FILE* outputFilePointer = fopen(outputFile, "w");
	if (outputFilePointer == NULL) {
		printf("Can't write %s\n", outputFile);
		exit(1);
	}

	int nSV = 0;
	int pSV = 0;
	for(int i = 0; i < nPoints; i++) {
		if (alpha[i] > epsilon) {
			if (labels[i] > 0) {
				pSV++;
			} else {
				nSV++;
			}
		}
	}

	bool printGamma = false;
	bool printCoef0 = false;
	bool printDegree = false;
	const char* kernelType = kp.kernel_type.c_str();
	if (strncmp(kernelType, "polynomial", 10) == 0) {
		printGamma = true;
		printCoef0 = true;
		printDegree = true;
	} else if (strncmp(kernelType, "rbf", 3) == 0) {
		printGamma = true;
	} else if (strncmp(kernelType, "sigmoid", 7) == 0) {
		printGamma = true;
		printCoef0 = true;
	}

	fprintf(outputFilePointer, "svm_type c_svc\n");
	fprintf(outputFilePointer, "kernel_type %s\n", kp.kernel_type.c_str());
	if (printDegree) {
		fprintf(outputFilePointer, "degree %i\n", kp.degree);
	}
	if (printGamma) {
		fprintf(outputFilePointer, "gamma %f\n", kp.gamma);
	}
	if (printCoef0) {
		fprintf(outputFilePointer, "coef0 %f\n", kp.coef0);
	}
	fprintf(outputFilePointer, "nr_class 2\n");
	fprintf(outputFilePointer, "total_sv %d\n", nSV + pSV);
	fprintf(outputFilePointer, "rho %.10f\n", kp.b);
	fprintf(outputFilePointer, "label 1 -1\n");
	fprintf(outputFilePointer, "nr_sv %d %d\n", pSV, nSV);
	fprintf(outputFilePointer, "SV\n");
	for (int i = 0; i < nPoints; i++) {
		if (alpha[i] > epsilon) {
			fprintf(outputFilePointer, "%.10f ", labels[i]*alpha[i]);
			for (int j = 0; j < nDimension; j++) {
				fprintf(outputFilePointer, "%d:%.10f ", j+1, data[j*nPoints + i]);
			}
			fprintf(outputFilePointer, "\n");
		}
	}
	fclose(outputFilePointer);
}

void printClassification(const char* outputFile, float* result, int nPoints) {
	FILE* outputFilePointer = fopen(outputFile, "w");
	if (outputFilePointer == NULL) {
		printf("Can't write %s\n", outputFile);
		exit(1);
	}
	for (int i = 0; i < nPoints; i++) {
		fprintf(outputFilePointer, "%f\n", result[i]);
	}
	fclose(outputFilePointer);
}
